﻿// - Schnelles Verschieben zwischen Monitoren mit unterschiedlicher Auflösung führt zum relativen Verschieben der Maus zum Fenster
// - Aero-snap auf falschen monitor (immer 2) bei stark üerlappendem fenster und monitor 2 größer als / gleich groß wie monitor 1

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;

using advadev.Windows.CustomWindows.WindowsGDI;


namespace advadev.Windows.CustomWindows
{

    /// <summary>
    /// Represents a custom window
    /// </summary>
    [TemplatePart(Name = "PART_DragMoveArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_LeftResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_LeftTopResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_TopResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_RightTopResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_RightResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_RightBottomResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_BottomResizeArea", Type = typeof(UIElement))]
    [TemplatePart(Name = "PART_LeftBottomResizeArea", Type = typeof(UIElement))]
    public class CustomWindow : Window
    {

        /// <summary>for internal use</summary>
        private UIElement _dragMoveArea;
        /// <summary>for internal use</summary>
        private UIElement _leftResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _leftTopResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _topResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _rightTopResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _rightResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _rightBottomResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _bottomResizeArea;
        /// <summary>for internal use</summary>
        private UIElement _leftBottomResizeArea;
        /// <summary>for internal use</summary>
        private double _calculatedMaxHeight;
        /// <summary>for internal use</summary>
        private double _calculatedMaxWidth;
        /// <summary>for internal use</summary>
        private double _userDefinedMaxHeight;
        /// <summary>for internal use</summary>
        private double _userDefinedMaxWidth;
        /// <summary>for internal use</summary>
        private double _restoreLeft;
        /// <summary>for internal use</summary>
        private double _restoreTop;


        /// <summary>
        /// Initializes the CustomWindow class
        /// </summary>
        static CustomWindow()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
            CloseCommand = new RoutedUICommand();
            MaximizeCommand = new RoutedUICommand();
            MinimizeCommand = new RoutedUICommand();
            NormalizeCommand = new RoutedUICommand();
            SwapWindowStateCommand = new RoutedUICommand();
        }

        /// <summary>
        /// Initializes a new instance of the CustomWindow class
        /// </summary>
        public CustomWindow()
        {
            AllowsTransparency = true;
            WindowStyle = WindowStyle.None;
            SourceInitialized += new EventHandler((object sender, EventArgs e) =>
            {
                try
                {
                    HwndSource.FromHwnd(new WindowInteropHelper(this).Handle).AddHook(new HwndSourceHook(windowProc));
                }
                catch { }
            });
            CommandBindings.Add(new CommandBinding(CloseCommand, new ExecutedRoutedEventHandler((object sender, ExecutedRoutedEventArgs args) =>
            {
                Close();
            }), new CanExecuteRoutedEventHandler((object sender, CanExecuteRoutedEventArgs args) =>
            {
                args.CanExecute = true;
            })));
            CommandBindings.Add(new CommandBinding(MaximizeCommand, new ExecutedRoutedEventHandler((object sender, ExecutedRoutedEventArgs args) =>
            {
                WindowState = WindowState.Maximized;
            }), new CanExecuteRoutedEventHandler((object sender, CanExecuteRoutedEventArgs args) =>
            {
                args.CanExecute = (ResizeMode == ResizeMode.CanResize);
            })));
            CommandBindings.Add(new CommandBinding(MinimizeCommand, new ExecutedRoutedEventHandler((object sender, ExecutedRoutedEventArgs args) =>
            {
                WindowState = WindowState.Minimized;
            }), new CanExecuteRoutedEventHandler((object sender, CanExecuteRoutedEventArgs args) =>
            {
                args.CanExecute = true;
            })));
            CommandBindings.Add(new CommandBinding(NormalizeCommand, new ExecutedRoutedEventHandler((object sender, ExecutedRoutedEventArgs args) =>
            {
                WindowState = WindowState.Normal;
            }), new CanExecuteRoutedEventHandler((object sender, CanExecuteRoutedEventArgs args) =>
            {
                args.CanExecute = (ResizeMode == ResizeMode.CanResize);
            })));
            CommandBindings.Add(new CommandBinding(SwapWindowStateCommand, new ExecutedRoutedEventHandler((object sender, ExecutedRoutedEventArgs args) =>
            {
                WindowState = (WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
            }), new CanExecuteRoutedEventHandler((object sender, CanExecuteRoutedEventArgs args) =>
            {
                args.CanExecute = (ResizeMode == ResizeMode.CanResize);
            })));
            // -->> ISSUE #0003 (Part 1)
            // Description: Wrong window size in monitor 2 if monitor 2 is bigger than monitor 1
            // Steps to reproduce:
            // (1) Attach a second monitor
            // (2) Set screen resolution of monitor 2 to a bigger value than for monitor 1
            // (3) Maximize the window in monitor 2
            // Resulting problem: Maximized window has the wrong size (the window is too big)
            // Solution: Use MaxHeight and MaxWidth and limit the window size by that values (but
            //           use those values only if the window is maximized and then use the minimum
            //           of the values calculated during the getminmaxinfo method and the user
            //           defined values)
            ensureMaxScreenSizeSetting();
            // <<-- ISSUE
            // -->> ISSUE #0004
            // Description: Aero-snap of a window overlapping between two monitors maximizes with
            //              wrong size (sometimes)
            // Steps to reproduce:
            // (1) Attach a second monitor with a different (e.g. lower) resolution than monitor 1
            // (2) Drag a window on the left side of the window drag area
            // (3) Raise the aero-snap with mouse in monitor 1 and maximum of the window in monitor 2
            // Resulting problem: Aero-snap snaps to corrent monitor (monitor 1) but with the wrong
            //                    size (size of monitor 2)
            // Solution: Minimize window and reposition it temporary to left top edge of the monitor
            //           with the mouse in to force recalculation of the minmaxinfo and re-maximize
            //           the window
            ensureMaximizedMonitor();
            // <<-- ISSUE
        }


        /// <summary>
        /// Gets the close command
        /// </summary>
        static public RoutedUICommand CloseCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the maximize command
        /// </summary>
        static public RoutedUICommand MaximizeCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the minimize command
        /// </summary>
        static public RoutedUICommand MinimizeCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the normalize command
        /// </summary>
        static public RoutedUICommand NormalizeCommand
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the swap window state command
        /// </summary>
        static public RoutedUICommand SwapWindowStateCommand
        {
            get;
            private set;
        }


        /// <summary>
        /// Applies the template
        /// </summary>
        override public void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            if (_dragMoveArea != null)
            {
                _dragMoveArea.MouseMove -= dragMoveArea_MouseMove;
                _dragMoveArea.MouseLeftButtonDown -= dragMoveArea_MouseLeftButtonDown;
                _dragMoveArea = null;
            }
            if (_leftResizeArea != null)
            {
                _leftResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _leftResizeArea = null;
            }
            if (_leftTopResizeArea != null)
            {
                _leftTopResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _leftTopResizeArea = null;
            }
            if (_topResizeArea != null)
            {
                _topResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _topResizeArea = null;
            }
            if (_rightTopResizeArea != null)
            {
                _rightTopResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _rightTopResizeArea = null;
            }
            if (_rightResizeArea != null)
            {
                _rightResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _rightResizeArea = null;
            }
            if (_rightBottomResizeArea != null)
            {
                _rightBottomResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _rightBottomResizeArea = null;
            }
            if (_bottomResizeArea != null)
            {
                _bottomResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _bottomResizeArea = null;
            }
            if (_leftBottomResizeArea != null)
            {
                _leftBottomResizeArea.PreviewMouseLeftButtonDown -= resizeArea_PreviewMouseLeftButtonDown;
                _leftBottomResizeArea = null;
            }
            if (Template != null)
            {
                _dragMoveArea = Template.FindName("PART_DragMoveArea", this) as UIElement;
                if (_dragMoveArea != null)
                {
                    _dragMoveArea.MouseLeftButtonDown += dragMoveArea_MouseLeftButtonDown;
                    _dragMoveArea.MouseMove += dragMoveArea_MouseMove;
                }
                _leftResizeArea = Template.FindName("PART_LeftResizeArea", this) as UIElement;
                if (_leftResizeArea != null)
                {
                    _leftResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _leftTopResizeArea = Template.FindName("PART_LeftTopResizeArea", this) as UIElement;
                if (_leftTopResizeArea != null)
                {
                    _leftTopResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _topResizeArea = Template.FindName("PART_TopResizeArea", this) as UIElement;
                if (_topResizeArea != null)
                {
                    _topResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _rightTopResizeArea = Template.FindName("PART_RightTopResizeArea", this) as UIElement;
                if (_rightTopResizeArea != null)
                {
                    _rightTopResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _rightResizeArea = Template.FindName("PART_RightResizeArea", this) as UIElement;
                if (_rightResizeArea != null)
                {
                    _rightResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _rightBottomResizeArea = Template.FindName("PART_RightBottomResizeArea", this) as UIElement;
                if (_rightBottomResizeArea != null)
                {
                    _rightBottomResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _bottomResizeArea = Template.FindName("PART_BottomResizeArea", this) as UIElement;
                if (_bottomResizeArea != null)
                {
                    _bottomResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
                _leftBottomResizeArea = Template.FindName("PART_LeftBottomResizeArea", this) as UIElement;
                if (_leftBottomResizeArea != null)
                {
                    _leftBottomResizeArea.PreviewMouseLeftButtonDown += resizeArea_PreviewMouseLeftButtonDown;
                }
            }
        }

        /// <summary>
        /// Handles the "MouseLeftButtonDown" event of the drag move area
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void dragMoveArea_MouseLeftButtonDown(object sender, MouseButtonEventArgs args)
        {
            if (args.ClickCount == 2)
            {
                // -->> ISSUE #0002
                // Description: Problem while maximizing a window by double click which is positioned almost at top
                // Steps to reproduce:
                // (1) Move window almost to the top of the screen
                // (2) Maximize the window by double click
                // Resulting problem: This "MouseMove" event will be raised (not because the mouse is moved but
                //                    because the window is moved relative to the mouse. And a mouse move event
                //                    in the drag move area while a window is maximized and the left mouse button
                //                    is pressed will raise the aero-unsap implementation (aero unsnap is
                //                    implemented in that way in this class; maybe a better solution can be found)
                // Solution: remove mouse move handler temporary (if necessary)
                IntPtr monitor = DLLIMPORTS.MonitorFromWindow(new WindowInteropHelper(this).Handle, DEFINES.MONITOR_DEFAULTTONEAREST);
                if (monitor != IntPtr.Zero)
                {
                    MONITORINFO monitorInfo = new MONITORINFO();
                    if (DLLIMPORTS.GetMonitorInfo(monitor, monitorInfo))
                    {
                        HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                        Matrix matrix = source.CompositionTarget.TransformFromDevice;
                        Point dpiIndependentSize = matrix.Transform(new Point(monitorInfo.rcWork.left, monitorInfo.rcWork.top));
                        if (Left != dpiIndependentSize.X || Top != dpiIndependentSize.Y)
                        {
                            MouseEventHandler handler = null;
                            handler = new MouseEventHandler((object s, MouseEventArgs a) =>
                            {
                                _dragMoveArea.MouseMove -= handler;
                                _dragMoveArea.MouseMove += dragMoveArea_MouseMove;
                            });
                            _dragMoveArea.MouseMove -= dragMoveArea_MouseMove;
                            _dragMoveArea.MouseMove += handler;
                        }
                    }
                }
                // <<-- ISSUE
                WindowState = (WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized);
                args.Handled = true;
                return;
            }
            if (args.LeftButton == MouseButtonState.Pressed)
            {
                DragMove();
                args.Handled = true;
                return;
            }
        }

        /// <summary>
        /// Handles the "MouseMove" event of the drag move area
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void dragMoveArea_MouseMove(object sender, MouseEventArgs args)
        {
            if (WindowState == WindowState.Maximized)
            {
                if (args.LeftButton == MouseButtonState.Pressed)
                {
                    double newTop = 0d;
                    IntPtr monitor = DLLIMPORTS.MonitorFromWindow(new WindowInteropHelper(this).Handle, DEFINES.MONITOR_DEFAULTTONEAREST);
                    if (monitor != IntPtr.Zero)
                    {
                        MONITORINFO monitorInfo = new MONITORINFO();
                        if (DLLIMPORTS.GetMonitorInfo(monitor, monitorInfo))
                        {
                            double restoreWidth = RestoreBounds.Width;
                            Point mousePoint = Mouse.GetPosition(this as IInputElement);
                            HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                            Matrix matrix = source.CompositionTarget.TransformFromDevice;
                            Point dpiIndependentSize = matrix.Transform(new Point(monitorInfo.rcMonitor.left, monitorInfo.rcMonitor.right));
                            Left = dpiIndependentSize.X + mousePoint.X - (restoreWidth / 2d);
                            if (monitorInfo.rcMonitor.left == 0)
                            {
                                Left = Math.Max(0, Left);
                            }
                            if (dpiIndependentSize.Y == SystemParameters.VirtualScreenWidth)
                            {
                                Left = Math.Min(dpiIndependentSize.Y - restoreWidth, Left);
                            }
                            dpiIndependentSize = matrix.Transform(new Point(Math.Abs(monitorInfo.rcWork.left - monitorInfo.rcMonitor.left), Math.Abs(monitorInfo.rcWork.top - monitorInfo.rcMonitor.top)));
                            newTop = dpiIndependentSize.Y;
                        }
                    }
                    Top = newTop;
                    _restoreTop = Top;
                    _restoreLeft = Left;
                    WindowState = WindowState.Normal;
                    // -->> ISSUE #0001
                    // Description: Wrong top position when using the aero-unspan the first time (sometimes)
                    // Steps to reproduce:
                    // (1) start the application and maximize the window without any other action / movement
                    // (2) use the aero-unsnap to resize the window
                    // Resulting problem: The window TOP is restored not to the Top value as set above
                    // Solution: Set the window top position again
                    Top = newTop;
                    // <<-- ISSUE
                    MouseButtonEventHandler handler = null;
                    handler = new MouseButtonEventHandler((object s, MouseButtonEventArgs a) =>
                    {
                        Mouse.OverrideCursor = null;
                        PreviewMouseLeftButtonUp -= handler;
                    });
                    PreviewMouseLeftButtonUp += handler;
                    Mouse.OverrideCursor = Cursors.Arrow;
                    DragMove();
                    args.Handled = true;
                }
            }
        }

        /// <summary>
        /// Handles the "PreviewMouseLeftButtonDown" event of a resize area
        /// </summary>
        /// <param name="sender">
        /// Sender of the event
        /// </param>
        /// <param name="args">
        /// Event argument
        /// </param>
        private void resizeArea_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
        {
            int scSizeDirection = -1;
            scSizeDirection = (sender == _leftResizeArea ? DEFINES.SC_SIZE_HTLEFT : scSizeDirection);
            scSizeDirection = (sender == _leftTopResizeArea ? DEFINES.SC_SIZE_HTTOPLEFT : scSizeDirection);
            scSizeDirection = (sender == _topResizeArea ? DEFINES.SC_SIZE_HTTOP : scSizeDirection);
            scSizeDirection = (sender == _rightTopResizeArea ? DEFINES.SC_SIZE_HTTOPRIGHT : scSizeDirection);
            scSizeDirection = (sender == _rightResizeArea ? DEFINES.SC_SIZE_HTRIGHT : scSizeDirection);
            scSizeDirection = (sender == _rightBottomResizeArea ? DEFINES.SC_SIZE_HTBOTTOMRIGHT : scSizeDirection);
            scSizeDirection = (sender == _bottomResizeArea ? DEFINES.SC_SIZE_HTBOTTOM : scSizeDirection);
            scSizeDirection = (sender == _leftBottomResizeArea ? DEFINES.SC_SIZE_HTBOTTOMLEFT : scSizeDirection);
            if (scSizeDirection != -1)
            {
                Mouse.OverrideCursor = (sender is FrameworkElement ? (sender as FrameworkElement).Cursor : null);
                DLLIMPORTS.SendMessage(HwndSource.FromHwnd(new WindowInteropHelper(this).Handle).Handle, DEFINES.WM_SYSCOMMAND, (IntPtr)(DEFINES.SC_SIZE + scSizeDirection), IntPtr.Zero);
                Mouse.OverrideCursor = null;
            }
        }

        /// <summary>
        /// Ensures the max screen size settings
        /// </summary>
        private void ensureMaxScreenSizeSetting()
        {
            _userDefinedMaxHeight = MaxHeight;
            _userDefinedMaxWidth = MaxWidth;
            DependencyPropertyDescriptor windowStateDescriptor = DependencyPropertyDescriptor.FromProperty(Window.WindowStateProperty, typeof(CustomWindow));
            if (windowStateDescriptor != null)
            {
                windowStateDescriptor.AddValueChanged(this, (object sender, EventArgs args) =>
                {
                    if (WindowState == WindowState.Maximized)
                    {
                        double userDefinedMaxHeight = _userDefinedMaxHeight;
                        MaxHeight = Math.Min(userDefinedMaxHeight, _calculatedMaxHeight);
                        _userDefinedMaxHeight = userDefinedMaxHeight;
                        double userDefinedMaxWidth = _userDefinedMaxWidth;
                        MaxWidth = Math.Min(userDefinedMaxWidth, _calculatedMaxWidth);
                        _userDefinedMaxWidth = userDefinedMaxWidth;
                    }
                    else
                    {
                        MaxHeight = _userDefinedMaxHeight;
                        MaxWidth = _userDefinedMaxWidth;
                    }
                });
            }
            DependencyPropertyDescriptor maxHeightDescriptor = DependencyPropertyDescriptor.FromProperty(Window.MaxHeightProperty, typeof(CustomWindow));
            if (maxHeightDescriptor != null)
            {
                maxHeightDescriptor.AddValueChanged(this, (object sender, EventArgs args) =>
                {
                    _userDefinedMaxHeight = MaxHeight;
                    if (WindowState == WindowState.Maximized)
                    {
                        double userDefinedMaxHeight = _userDefinedMaxHeight;
                        MaxHeight = Math.Min(userDefinedMaxHeight, _calculatedMaxHeight);
                        _userDefinedMaxHeight = userDefinedMaxHeight;
                    }
                });
            }
            DependencyPropertyDescriptor maxWidthDescriptor = DependencyPropertyDescriptor.FromProperty(Window.MaxWidthProperty, typeof(CustomWindow));
            if (maxWidthDescriptor != null)
            {
                maxWidthDescriptor.AddValueChanged(this, (object sender, EventArgs args) =>
                {
                    _userDefinedMaxWidth = MaxWidth;
                    if (WindowState == WindowState.Maximized)
                    {
                        double userDefinedMaxWidth = _userDefinedMaxWidth;
                        MaxWidth = Math.Min(userDefinedMaxWidth, _calculatedMaxWidth);
                        _userDefinedMaxWidth = userDefinedMaxWidth;
                    }
                });
            }
        }

        /// <summary>
        /// Ensures the maximize monitor
        /// </summary>
        private void ensureMaximizedMonitor()
        {
            DependencyPropertyDescriptor windowStateDescriptor = DependencyPropertyDescriptor.FromProperty(Window.WindowStateProperty, typeof(CustomWindow));
            EventHandler handler = null;
            handler = new EventHandler((object sender, EventArgs args) =>
            {
                if (WindowState != WindowState.Maximized)
                {
                    Left = _restoreLeft;
                    Top = _restoreTop;
                    windowStateDescriptor.RemoveValueChanged(this, handler);
                }
            });
            SizeChanged += new SizeChangedEventHandler((object sender, SizeChangedEventArgs args) =>
            {
                if (WindowState == WindowState.Maximized)
                {
                    IntPtr windowMonitor = DLLIMPORTS.MonitorFromWindow(new WindowInteropHelper(this).Handle, DEFINES.MONITOR_DEFAULTTONEAREST);
                    if (windowMonitor != IntPtr.Zero)
                    {
                        MONITORINFO windowMonitorInfo = new MONITORINFO();
                        if (DLLIMPORTS.GetMonitorInfo(windowMonitor, windowMonitorInfo))
                        {
                            HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                            Matrix matrix = source.CompositionTarget.TransformFromDevice;
                            Point dpiIndependentSize = matrix.Transform(new Point((windowMonitorInfo.rcWork.right - windowMonitorInfo.rcWork.left), (windowMonitorInfo.rcWork.bottom - windowMonitorInfo.rcWork.top)));
                            double diffX = ActualWidth - dpiIndependentSize.X;
                            double diffY = ActualHeight - dpiIndependentSize.Y;
                            double autoHideTaskbarOffset = 1;
                            if (Math.Abs(diffX) > autoHideTaskbarOffset || Math.Abs(diffY) > autoHideTaskbarOffset)
                            {
                                double opacity = Opacity;
                                dpiIndependentSize = matrix.Transform(new Point(windowMonitorInfo.rcWork.left, windowMonitorInfo.rcWork.top));
                                Opacity = 0;
                                WindowState = WindowState.Normal;
                                _restoreLeft = Left;
                                _restoreTop = Top;
                                Left = dpiIndependentSize.X;
                                Top = dpiIndependentSize.Y;
                                WindowState = WindowState.Maximized;
                                if (windowStateDescriptor != null)
                                {
                                    windowStateDescriptor.AddValueChanged(this, handler);
                                }
                                Opacity = opacity;
                            }
                        }
                    }
                }
            });
        }

        /// <summary>
        /// Handles window proc calls
        /// </summary>
        /// <param name="hwnd">
        /// A handle to the window
        /// </param>
        /// <param name="uMsg">
        /// The message
        /// </param>
        /// <param name="wParam">
        /// Additional message information. The contents of this parameter
        /// depend on the value of the uMsg parameter
        /// </param>
        /// <param name="lParam">
        /// Additional message information. The contents of this parameter
        /// depend on the value of the uMsg parameter
        /// </param>
        /// <param name="handled">
        /// Flag indicating whether the window proc call was handled
        /// </param>
        /// <returns>
        /// The return value is the result of the message processing and depends
        /// on the message sent
        /// </returns>
        private IntPtr windowProc(IntPtr hwnd, int uMsg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (uMsg)
            {
                case DEFINES.WM_GETMINMAXINFO:
                    wmGetMinMaxInfo(hwnd, lParam);
                    handled = true;
                    break;
            }
            return (IntPtr)0;
        }

        /// <summary>
        /// Gets the min max info for the window manager
        /// </summary>
        /// <param name="hwnd">
        /// A handle to the window
        /// </param>
        /// <param name="lParam">
        /// Additional message information. The contents of this parameter
        /// depend on the value of the uMsg parameter
        /// </param>
        private void wmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
        {
            MINMAXINFO minMaxInfo = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
            IntPtr windowMonitor = DLLIMPORTS.MonitorFromWindow(hwnd, DEFINES.MONITOR_DEFAULTTONEAREST);
            if (windowMonitor != IntPtr.Zero)
            {
                MONITORINFO windowMonitorInfo = new MONITORINFO();
                if (DLLIMPORTS.GetMonitorInfo(windowMonitor, windowMonitorInfo))
                {
                    minMaxInfo.ptMaxPosition.x = Math.Abs(windowMonitorInfo.rcWork.left - windowMonitorInfo.rcMonitor.left);
                    minMaxInfo.ptMaxPosition.y = Math.Abs(windowMonitorInfo.rcWork.top - windowMonitorInfo.rcMonitor.top);
                    minMaxInfo.ptMaxSize.x = Math.Abs(windowMonitorInfo.rcWork.right - windowMonitorInfo.rcWork.left);
                    minMaxInfo.ptMaxSize.y = Math.Abs(windowMonitorInfo.rcWork.bottom - windowMonitorInfo.rcWork.top);
                    // -->> ISSUE #0003 (Part 2)
                    // Description: Wrong window size in monitor 2 if monitor 2 is bigger than monitor 1
                    // Steps to reproduce:
                    // (1) Attach a second monitor
                    // (2) Set screen resolution of monitor 2 to a bigger value than for monitor 1
                    // (3) Maximize the window in monitor 2
                    // Resulting problem: Maximized window has the wrong size (the window is too big)
                    // Solution: Use MaxHeight and MaxWidth and limit the window size by that values (but
                    //           use those values only if the window is maximized and then use the minimum
                    //           of the values calculated during the getminmaxinfo method and the user
                    //           defined values)
                    HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
                    Matrix matrix = source.CompositionTarget.TransformFromDevice;
                    Point dpiIndependentSize = matrix.Transform(new Point(minMaxInfo.ptMaxSize.x, minMaxInfo.ptMaxSize.y));
                    _calculatedMaxHeight = dpiIndependentSize.Y;
                    _calculatedMaxWidth = dpiIndependentSize.X;
                    // <<-- ISSUE
                    Matrix inverseMatrix = source.CompositionTarget.TransformToDevice;
                    dpiIndependentSize = inverseMatrix.Transform(new Point(MinWidth, MinHeight));
                    minMaxInfo.ptMinTrackSize.x = (int)Math.Round(dpiIndependentSize.X);
                    minMaxInfo.ptMinTrackSize.y = (int)Math.Round(dpiIndependentSize.Y);
                    if (isTaskBarAutoHideFlagSet() == true)
                    {
                        switch (getTaskBarPosition())
                        {
                            case DEFINES.ABE_LEFT:
                                minMaxInfo.ptMaxPosition.x += 1;
                                minMaxInfo.ptMaxSize.x -= 1;
                                break;
                            case DEFINES.ABE_TOP:
                                minMaxInfo.ptMaxPosition.y += 1;
                                minMaxInfo.ptMaxSize.y -= 1;
                                break;
                            case DEFINES.ABE_RIGHT:
                                minMaxInfo.ptMaxSize.x -= 1;
                                break;
                            case DEFINES.ABE_BOTTOM:
                                minMaxInfo.ptMaxSize.y -= 1;
                                break;
                        }
                    }
                }
            }
            Marshal.StructureToPtr(minMaxInfo, lParam, true);
        }

        /// <summary>
        /// Gets a flag indicating whether the auto hide flag of the task bar is
        /// set
        /// </summary>
        /// <returns>
        /// True if the auto hide flag is set for the task bar, null if no task
        /// bar found, false otherwise
        /// </returns>
        private bool? isTaskBarAutoHideFlagSet()
        {
            IntPtr taskBarWindow = DLLIMPORTS.FindWindow("Shell_TrayWnd", null);
            if (taskBarWindow != null)
            {
                APPBARDATA appBarData = new APPBARDATA();
                appBarData.cbSize = Marshal.SizeOf(appBarData);
                appBarData.hWnd = taskBarWindow;
                int state = DLLIMPORTS.SHAppBarMessage((int)DEFINES.ABM_GETSTATE, ref appBarData);
                return ((state & DEFINES.ABS_AUTOHIDE) == DEFINES.ABS_AUTOHIDE);
            }
            return null;
        }

        /// <summary>
        /// Gets the task bar position as interger value
        /// </summary>
        /// <returns>
        /// The task bar position as interger value:
        /// - 0: task bar not found
        /// - 1: left
        /// - 2: top
        /// - 3: right
        /// - 4: bottom
        /// </returns>
        private int getTaskBarPosition()
        {
            IntPtr taskBarWindow = DLLIMPORTS.FindWindow("Shell_TrayWnd", null);
            if (taskBarWindow != null)
            {
                APPBARDATA appBarData = new APPBARDATA();
                appBarData.cbSize = Marshal.SizeOf(appBarData);
                appBarData.hWnd = taskBarWindow;
                DLLIMPORTS.SHAppBarMessage((int)DEFINES.ABM_GETTASKBARPOS, ref appBarData);
                return appBarData.uEdge;
            }
            return 0;
        }

        /// <summary>
        /// Gets the task bar bounds
        /// </summary>
        /// <returns>
        /// Task bar bounds (empty if no task bar found)
        /// </returns>
        private Rect getTaskBarBounds()
        {
            Rect result = new Rect();
            IntPtr taskBarWindow = DLLIMPORTS.FindWindow("Shell_TrayWnd", null);
            if (taskBarWindow != null)
            {
                APPBARDATA appBarData = new APPBARDATA();
                appBarData.cbSize = Marshal.SizeOf(appBarData);
                appBarData.hWnd = taskBarWindow;
                DLLIMPORTS.SHAppBarMessage((int)DEFINES.ABM_GETTASKBARPOS, ref appBarData);
                result = new Rect(appBarData.rc.left, appBarData.rc.top, (appBarData.rc.right - appBarData.rc.left), (appBarData.rc.bottom - appBarData.rc.top));
            }
            return result;
        }

    }

}
